package main

import (
	"encoding/json"
	"fmt"
)

// ==========结构体==========

type Cat struct {
	Name  string // Name就是字段或者属性
	Age   int
	Color string
	Hobby string
	Fight [3]int
}

type Person struct {
	ptr   *Cat
	slice []int
	mp1   map[string]string
}

type Point struct {
	x int
	y int
}

type Rect struct {
	leftUp, rightDown Point
}

type Rect2 struct {
	leftUp, rightDown *Point
}

type A struct {
	Num int
}

type B struct {
	Num int
}

type Monster struct {
	Name  string `json:"name"` // `json:"name"`就是结构体的tag
	Age   int    `json:"age"`
	Skill string `json:"skill"`
}

func main() {
	// golang语言面向对象编程说明
	/*
		1、golang也支持面向对象编程, 但和传统的面向对象编程有区别, 所以说Golang支持面向对象编程特性是比较准确的
		2、golang没有类(class), go语言的结构体(struct)和其他编程语言的类有同等的地位, 可以理解为golang是基于struct来实现OOP特效的
		3、golang面向对象编程非常简介, 去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等
		4、golang仍然有面向对象编程的继承、封装和多态的特性, 只是实现的方式和其他OOP语言不一样
	*/

	// 第一种构造结构体变量的方式, 不必对所有字段都赋值
	var cat Cat
	cat.Name = "小花"
	cat.Age = 3
	cat.Color = "黑色"
	cat.Hobby = "吃鱼"
	cat.Fight = [...]int{1, 2, 3}
	fmt.Println(cat) // {小花 3 黑色 吃鱼 [1 2 3]}
	// 第二种构造结构体变量的方式, 不指定字段名, 不好的地方是必须所有字段都赋值
	cat2 := Cat{"小红", 4, "灰色", "睡觉", [3]int{1, 2, 3}}
	fmt.Println(cat2) // {小红 4 灰色 睡觉 [1 2 3]}
	// 第二种构造结构体变量的方式变体, 不必对所有字段赋值
	cat3 := Cat{
		Name:  "小红~",
		Color: "灰色",
		Hobby: "睡觉",
	}
	fmt.Println(cat3) // {小红~ 0 灰色 睡觉 [0 0 0]}
	// 第三种构造结构体变量的方式
	var p0 = Person{} // {<nil> [] map[]}
	p0.slice = make([]int, 1)
	fmt.Println(p0) // {<nil> [0] map[]}
	// 第四种, 使用new创建一个Monster指针, 返回结构体指针
	var m = new(Monster)
	m.Name = "孙悟空" // 使用指针可以对结构体的字段直接赋值, 等价于(*m).Name = "孙悟空"
	fmt.Println(m) // &{孙悟空 0 }
	/*
		var aaa = new([3]int)
		aaa[0] = 1
		fmt.Println(aaa)  // &[1 0 0]
	*/
	// 第五种, 底层会在mon前加上*, 返回结构体指针
	var mon = &Monster{"yoyo", 10, "sleep"}
	mon.Name = "AK"
	(*mon).Age = 100
	fmt.Println(mon) // &{AK 100 sleep}

	// 不同结构体变量的名字独立, 互补影响, 一个结构体变量字段的更改, 不会影响另一个结构体变量
	// 结构体是值类型, 默认为值拷贝
	var monster1 Monster
	monster1.Name = "牛魔王"
	monster1.Age = 500
	monster2 := monster1
	monster2.Name = "青牛精"
	monster2.Age = 1000
	fmt.Println(monster1) // {牛魔王 500 }
	fmt.Println(monster2) // {青牛精 1000 }

	// 结构体的注意事项和使用细节
	// 1、结构体的所有字段在内存中是连续的
	r1 := Rect{Point{1, 2}, Point{3, 4}}
	fmt.Println(&r1.leftUp.x, &r1.leftUp.y)       // 0xc000092060 0xc000092068
	fmt.Println(&r1.rightDown.x, &r1.rightDown.y) // 0xc000092070 0xc000092078
	r2 := Rect2{&Point{1, 2}, &Point{3, 4}}
	fmt.Println(r2.rightDown.x)
	// 2、结构体进行类型重新定义, Golang认为是新的数据类型, 需要强制转换, 前提是结构体的字段完全一致(名字、类型、个数)
	var a A
	var b B
	a = A(b)          // 需要强制转换
	fmt.Println(a, b) // {0} {0}
	// 3、struct的每个字段上, 可以写上一个tag, 该tag可以通过反射机制获取, 常见的使用场景就是序列化和反序列化
	monster := Monster{"牛魔王", 500, "芭蕉扇"}
	// json.Marshal中使用了反射
	// Marshal(v interface{}) ([]byte, error)
	jsonMonster, err := json.Marshal(monster) // 将monster变量序列化为json格式字符串, 返回一个切片和error
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(string(jsonMonster)) // {"name":"牛魔王","age":500,"skill":"芭蕉扇"}
	}

}
